#include <vector>

#include <windows.h>
#include <SetupAPI.h>
#include <initguid.h>
#include <winioctl.h>

#include "log.h"
#include "VioFsAPI.h"
#include "fuse.h"
#include "virtiofs.h"

const int LOOKUP_VIRTIOFS_DEVICE_MAX = 4;

VioFsAPI::VioFsAPI()
    : m_bInitialized(FALSE),
      m_handle(INVALID_HANDLE_VALUE)
{ }

VioFsAPI::~VioFsAPI()
{
    if (m_handle != INVALID_HANDLE_VALUE) {
        CloseHandle(m_handle);
        m_handle = INVALID_HANDLE_VALUE;
    }
}

BOOL VioFsAPI::Initialize()
{
    if (m_bInitialized) {
        LOGW("VioFsAPI already initialised!");
        return TRUE;
    }

    if (!GetVioFsHandle() || m_handle == INVALID_HANDLE_VALUE) {
        LOGE("Failed to get VioFs handle");
        return FALSE;
    }

    m_bInitialized = TRUE;
    return TRUE;
}

BOOL VioFsAPI::GetVioFsHandle()
{
    HDEVINFO devInfo = SetupDiGetClassDevs((LPGUID)&GUID_DEVINTERFACE_VIRT_FS, nullptr, nullptr,
                                           (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
    if (devInfo == INVALID_HANDLE_VALUE) {
        LOGE("Cannot get class devices.");
        return FALSE;
    }

    SP_DEVICE_INTERFACE_DATA devIfaceData = { 0 };
    devIfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
    for (INT idx = 0; idx < LOOKUP_VIRTIOFS_DEVICE_MAX; ++idx) {
        if (!SetupDiEnumDeviceInterfaces(devInfo, 0, (LPGUID)&GUID_DEVINTERFACE_VIRT_FS, idx, &devIfaceData)) {
            continue;
        }

        DWORD reqSize = 0;
        SetupDiGetDeviceInterfaceDetail(devInfo, &devIfaceData, nullptr, 0, &reqSize, nullptr);
        std::vector<uint8_t> devDataBuf(reqSize);
        PSP_DEVICE_INTERFACE_DETAIL_DATA devIfaceDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)devDataBuf.data();

        devIfaceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
        ULONG length = reqSize;
        if (!SetupDiGetDeviceInterfaceDetail(devInfo, &devIfaceData, devIfaceDetail, length, &reqSize, nullptr)) {
            LOGE("Cannot get device interface details with error: {}", GetLastError());
            continue;
        }

        SECURITY_ATTRIBUTES securityAttributes;
        securityAttributes.nLength = sizeof(securityAttributes);
        securityAttributes.lpSecurityDescriptor = nullptr;
        securityAttributes.bInheritHandle = FALSE;

        m_handle = CreateFile(devIfaceDetail->DevicePath, GENERIC_READ | GENERIC_WRITE,
                              0, &securityAttributes, OPEN_EXISTING, 0, nullptr);
        if (m_handle == INVALID_HANDLE_VALUE) {
            LOGD("Failed to CreateFile with error: {}", GetLastError());
            continue;
        }

        break;
    }

    if (!SetupDiDestroyDeviceInfoList(devInfo)) {
        LOGE("SetupDiDestroyDeviceInfoList failed: {}", GetLastError());
    }
    return TRUE;
}

BOOL VioFsAPI::SubmitVioFsRequest(LPVOID inBuf, DWORD inBufSize, LPVOID outBuf, DWORD outBufSize)
{
    DWORD bytesReturned = 0;
    BOOL result = DeviceIoControl(m_handle, IOCTL_VIRTFS_FUSE_REQUEST, inBuf, inBufSize,
                                  outBuf, outBufSize, &bytesReturned, nullptr);
    if (!result) {
        LOGE("Failed to DeviceIoControl with error: {}", GetLastError());
        return FALSE;
    }

    if (bytesReturned < sizeof(fuse_out_header)) {
        LOGE("Bytes Returned: {}, less than: {}", bytesReturned, sizeof(fuse_out_header));
        return FALSE;
    }

    fuse_out_header *outHeader = (fuse_out_header *)outBuf;
    LOGD("Bytes Returned: {}, len: {}, error: {}, unique: {}",
         bytesReturned, outHeader->len, outHeader->error, outHeader->unique);

    if (bytesReturned != outHeader->len) {
        LOGE("Mismatch in Bytes Returned, Actual: {}, Expected: {}", bytesReturned, outHeader->len);
    }

    if (outHeader->error < 0) {
        LOGE("Return error: {}", outHeader->error);
        return FALSE;
    }

    return TRUE;
}
